﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq;
using System.Xml.Linq;
using System.IO;

namespace JQuerySharp
{
    class Program
    {

        static void Main(string[] args)
        {
            //record signatures of each added method to prevent
            //duplication
            List<string> signatures = new List<string>();

            XDocument apiDoc = XDocument.Load(
                Path.Combine(Environment.CurrentDirectory, "jquery-docs-xml.xml")
            );


            XDocument mappingDoc = XDocument.Load(
                Path.Combine(Environment.CurrentDirectory, "Mappings.xml")
            );

            string scriptSharpTemplate = File.ReadAllText(Path.Combine(Environment.CurrentDirectory, "ScriptSharpTemplate.cs.txt"));

            //get type mapping from jquery to C#
            Dictionary<string, string> dMappings = getMappings(mappingDoc);

            //get all methods
            IEnumerable<Method> methods = getMethods(apiDoc);
           
            StringBuilder sb = new StringBuilder();
            foreach (Method method in methods)
            {
                var par = (from p in method.XmlElm.Descendants()
                           where p.Name == "params"
                           select new
                           {
                               Name = p.Attribute("name").Value,
                               Types = p.Attribute("type").Value.Split('|')
                           }).ToList();

                //in some param tags in the api-xml the input parameter can be of two diffent types like String|Number
                //we assume that this only can happen in one of the parameters and handle it with a for loop
                //that possibly printes the method two times.. with the first and second parameter
                var bTwo =
                    (from x in par
                     where x.Types.Length == 2
                     select x).ToList().Count > 0;

                for (int i = 0; i < (bTwo ? 2 : 1); i++)
                {
                    string signature = method.Name + method.ReturnType;

                    string sParams = "";
                    int iPar = 0;

                    foreach (var p in par)
                    {
                        iPar++;

                        int paramIndex = (p.Types.Length == 2 && i == 1) ? 1 : 0;
                        bool isCallbackParameter = (p.Types[paramIndex] == "Function");

                        string alternateType = null;
                        if (isCallbackParameter)
                        {
                            //there is no data in the xml that describes the signature of the
                            //callback function, so check the mappings.xml to see if a special callback
                            //is mapped to this function name. If not, use the default Function mapping
                            //in mappings.xml

                            var forTag = (from tag in mappingDoc.Descendants()
                                          where tag.Name == "for"
                                          && tag.Attribute("method") != null && tag.Attribute("method").Value == method.Name
                                          select tag).SingleOrDefault();

                            if (forTag != null)
                            {
                                alternateType = forTag.Attribute("use").Value;
                            }
                        }

                        signature += p.Types[paramIndex];

                        //aviod using reserved word as parameter names
                        string name = p.Name;
                        if (p.Name == null) name = "val";
                        if (p.Name == "out") name = "Out";
                        if (p.Name == "class") name = "cssClass";
                        if (p.Name == "params") name = "parameters";

                        sParams += String.Format(
                            "{0} {1},",
                            alternateType != null ? alternateType : dMappings[p.Types[paramIndex]],
                            name
                        );
                    }


                    if (!signatures.Contains<string>(signature)
                        && method.Name != null
                        && method.ReturnType != null
                        && !method.Name.StartsWith("$")
                        )
                    {

                        //remove last comma
                        if (sParams.EndsWith(",")) { sParams = sParams.Substring(0, sParams.Length - 1); }

                        //the function is never actually called so the content 
                        //only needs to compile
                        string ret;
                        switch (method.ReturnType)
                        {
                            case "Boolean":
                                ret = "false";
                                break;
                            case "Number":
                                ret = "0";
                                break;
                            default:
                                ret = "null";
                                break;
                        }
                        ret = ("{return " + ret + ";}\r\n\r\n");

                        string methodName = method.Name;
                        if (method.Name == "is") methodName = "isInExpression";

                        sb.Append(
                            String.Format(
                                "{4}\r\n[PreserveCase]\r\npublic {0} {1}({2}){3}",
                                dMappings[method.ReturnType],
                                methodName,
                                sParams,
                                ret,
                                formatDescription(method.Description))
                        );
                    }

                    signatures.Add(signature);
                }
            }

            File.WriteAllText(
                Path.Combine(Path.Combine(Environment.CurrentDirectory, "../.."), "ScriptSharpGenerated.cs.txt"),
                scriptSharpTemplate.Replace("##", sb.ToString())
                );

            Console.Write(sb.ToString());
            Console.ReadLine();
        }

        private static void getJqueryTypes(XDocument apiDoc)
        {
            IEnumerable<string> distinctTypes =
                (from item in apiDoc.Descendants()
                 where item.Name == "params"
                 select item.Attribute("type").Value).Distinct().ToList<string>().ToArray<string>();
        }

        private static IEnumerable<Method> getMethods(XDocument apiDoc)
        {
            //get method from xml
            IEnumerable<Method> returnMethods = from item in apiDoc.Descendants()
                                                where item.Name == "method"
                                                select new Method
                                                {
                                                    Name = item.Attribute("name").Value,
                                                    ReturnType = item.Attribute("type").Value,
                                                    XmlElm = item,
                                                    Description = item.Descendants().Where(x => x.Name == "desc").FirstOrDefault().Value
                                                };
            return returnMethods;
        }

        private static Dictionary<string, string> getMappings(XDocument mappingDoc)
        {
            var mappings = from mapping in mappingDoc.Descendants()
                           where mapping.Name == "mapping"
                           select new { From = mapping.Attribute("from").Value, To = mapping.Attribute("to").Value };
            Dictionary<string, string> dMappings = new Dictionary<string, string>();
            foreach (var item in mappings)
            {
                dMappings[item.From] = item.To;
            }
            return dMappings;
        }

        private static string formatDescription(string p)
        {
            return "///<summary>" + p.Replace("\n", "\n///") + "</summary>";
        }
    }

    public class Method
    {
        public string Name { get; set; }
        public string ReturnType { get; set; }
        public XElement XmlElm { get; set; }
        public string Description { get; set; }
        public IEnumerable<MethodParameter> Parameters{get;set;}
    };

    public class MethodParameter
    {
        public string Name { get; set; }
        public string Type { get; set; }
    }
}


